# Configuration Tab module for CROSS

#########################################################################################
#
# Copyright (C) 2008  
# Authors: Aljosa Osep (aljosa.osep@gmail.com), Jernej Kranjec (jernej.kranjec@gmail.com)
# From Faculty of Electrical Engineering and Computer Science, Maribor, Slovenia
# Done at School of Technology, Seinajoki, Finland
# Under mentor Kimmo Salmenjoki (kimmo.salmenjoki@seamk.fi)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################################

# ==================== imports ====================== #
import sys
sys.path.append('Modules/GUI/Widgets')
sys.path.append('Modules/Core')

import pygtk            # import pygtk library
pygtk.require('2.0')    # require version 2.o
import gtk 	        # import gtk

import tabModule	# import abstract tab class
import binascii
import treeGenModule

import HDLC		# command parser


############################
# strRecord class
# class contains info
# and methods for data 
# that is parsed from string
# and passed to combo
############################
class strRecord:
	# methods
	def parseString(self, string):
		self.offset = int(string[0])				# read the offset
		if string[1] != ';':					# character followed must be ; 
			raise ValueError				# if it is not, raise ValueError

		self.titles = []					# storage for titles
		self.values = []					# storage for values

		
		self.value = ""
		i=2
		while i < len(string):					# loop through whole string
			if string[i] == ':':				# if series of characters are followed by : , then series is title
				self.titles.append(self.value)		# append title
				self.value = ""				# reset value
			elif string[i] == ';':				# if series of characters are followed by ; , then series is value
				self.values.append(self.value)		# append value
				self.value = ""				# reset value
			else:
				self.value = self.value + string[i]	# if current char is no termination char ( : or ; ), just append char to value store
			i = i + 1					# inc counter
		
	def __init__(self):
		pass


############################
# commandTab class
############################
class configTab(tabModule.abstractTab):
	# callbacks
	def on_cursor_changed(self, widget):				# TREE NODE CHANGED CALLBACK
		selection = self.treeview.get_selection()		# get current tree selection
		model, iter = selection.get_selected()			# and iterator

		self.titleLabel.set_text(self.store.get_value(iter, 0))	# store tree record info to class 
		self.currentRegister = self.store.get_value(iter, 1)	# -||-
		self.currentBit = self.store.get_value(iter, 2)		# -||-
		self.currentSelection = self.store.get_value(iter, 3)	# -||-
		self.currentString = self.store.get_value(iter, 4)	# -||-

		self.clear_status_labels()				# as name says. clear status labels ;)

		# read register
		if self.currentSelection != 0:				# if selection is anything else than 0 ...
			self.communicator.sendData("}Cr,d:" + self.currentRegister + "!" + '\r\n')	# send query
			tt = HDLC.CHUMP().parse(self.communicator.retriveData())			# parse it
			if tt.has_key("b:"):
				self.str_to_list(self.hex_to_bin(tt["b:"], 32), self.bitList)		# read data to bitList storage

#		for bit in self.bitList:
#			sys.stdout.write(str(bit))
#		print "end"

		# code below takes care that information, stored in maschine, is shown to user in
		# human-readable form(proper enable/disable button active, proper combo val selected, ...)

		if self.currentSelection == 1:				# curr_sel = 1 -> that means enable/disable buttons
			self.combo_box.hide()				# hide other stuff
			self.entry_box.hide()				# -||-
			self.radioEnable.show()				# -||-
			self.radioDisable.show()			# -||-
			self.fixedChild.show()				# -||-

			if self.bitList[31-self.currentBit] == 1:   	# if currentBit is True
				self.radioEnable.set_active(True)	# radio enabled should be active
			else:
				self.radioDisable.set_active(True)	# otherwise radio disable

		elif self.currentSelection == 2:			# curr_sel = 2 -> comboBox
			self.radioEnable.hide()				# hide other stuff
			self.radioDisable.hide()			# -||-
			self.entry_box.hide()				# -||-
			self.combo_box.show()				# -||-
			self.fixedChild.show()				# -||-

			self.combo_store.clear()			# clear previous combo values
			self.comboRecord.parseString(self.currentString) # parse current string to comboRecord
			for title in self.comboRecord.titles:		# based on parsed values, fill combo
				self.combo.append_text(title)

			string = ""
			i = 32-(self.currentBit+self.comboRecord.offset)	# find bit pattern
			while i<(32-self.currentBit):
				string = string + str(self.bitList[i])
		#		sys.stdout.write(str(i)+' ')
				i = i+1

			i = 0
			for val in self.comboRecord.values:			# when bit pattern is found, index that belong
				if val == string:				# to that pattern is in i
					break
				i  = i+1

			self.combo.set_active(i)				# make combo active field with index i



		elif self.currentSelection == 3:
			self.radioEnable.hide()			# hide other stuff
			self.radioDisable.hide()		# -||-
			self.combo_box.hide()			# -||-
			self.entry_box.show()			# -||-
			self.fixedChild.show()			# -||-

			bin_val = ""				# store for binary value in portion of register we're reading
			current_index = 32 - self.currentBit - int(self.currentString)	# current index is 32 - offset - pattern length
			top_index = 32 - self.currentBit	# top index is 32 - offset
			
			
			while current_index < top_index:	# loop through	proper protion of bitList reg
				bin_val = bin_val + str(self.bitList[current_index])	# add every character to bin_val storage
				current_index = current_index + 1			# inc counter

			self.entry.set_text(str(int(bin_val, 2)))			# convery bin_val to decimal and set entry text to this number 
		else:
			self.fixedChild.hide()			# currentSelection = 0 or > 3: dont show any configuration form


	def on_combo_changed(self, widget):			# event occurs when button with combo is clicked
		selected = self.combo.get_active()		# get active value in combo

#		for bit in self.bitList:
#			sys.stdout.write(str(bit))
		self.str_to_list_back(self.comboRecord.values[selected], self.bitList, self.currentBit) # write value to bitlist
		self.writeConfiguration(-1)			# and finally write configuration


	def on_text_changed(self, widget):			# event occurs when button with entry is clicked
		t_input = self.entry.get_text()			# gets text from entry

		# checks if input is valid number
		# valid input: 48 - 57 (ascii)
		for character in t_input:
			if ord(character) < 48 or ord(character) > 57:
				self.message_box("Invalid input, it should be numerical value.")
				return

		# convert input to base 10 and write bits to proper place in bitList 
		self.str_to_list_back(self.dec_to_bin(int(t_input), int(self.currentString)), self.bitList, self.currentBit) 	
		self.writeConfiguration(-1)			# send configuration to maschine

		

	def on_radio_enable(self, widget):	# event occurs when enable radio is clicked
		self.writeConfiguration(1)	# send configuration to maschine

	def on_radio_disable(self, widget):	# event occurs when disable radio is clicked
		self.writeConfiguration(0)	# send configuration to maschine

	def init_bitlist(self):			# init bitList with zeros and init comboRecord
		self.comboRecord = strRecord()
		self.bitList = []		# make list
		for i in range (32):		# reserve 32 places
			self.bitList.append(0)

	def clear_status_labels(self):		# clear both labels
		self.gen_str_lbl.set_text("Generated string: ")			
		self.rt_str_lbl.set_text("Returned string: ")	


	#===========================================
	# methods
	#===========================================

	# converts 4 bits into one hex number
	def bin_to_hex4(self, b_list):
		acc = 0
		for i in range(4):
			acc = acc + b_list[i]*pow(2, 3-i)

		if acc < 10:					# if it is less than 10
			return str(acc)				# it ok, just return num
		elif acc > 9 and acc <=16:			# if it is greater than 10
			return str(chr(55+acc))			# find proper character that represents hex value

	# converts 32 bit number into hex number
	def bin_to_hex32(self, b_list):
		hex_str = ""
		for i in range(8):
			hex_str = hex_str + str(self.bin_to_hex4(b_list[4*i:4*(i+1)]))	# I forgot how this method works exactly...
		return hex_str								# but it works :)

	# converts hex number into binary, returns string
	def hex_to_bin(self, n, count=24):
		dec = long(n, 16)		# hex -> dec
	    	#returns the binary of integer n, using count number of digits
		return "".join([str((dec >> y) & 1) for y in range(count-1, -1, -1)])

	# converts hex number into binary, returns string
	def dec_to_bin(self, n, count=24):
	    	#returns the binary of integer n, using count number of digits
		return "".join([str((n >> y) & 1) for y in range(count-1, -1, -1)])

	# converts string to list of integers
	def str_to_list(self, string, dat):
		i = 0
		for letter in string:			# just stores every character, converted in integer, to specified list
			dat[i] = int(letter)
			i = i + 1

	def str_to_list_back(self, string, dat, start): # special kind of str_to_list, it stores stuff from the back
		i = len(dat) - start - len(string)
		for letter in string:
			dat[i] = int(letter)
			i = i + 1

	# generates string to be sent to reader	
	def genSendString(self):
		if self.check.get_active() == True:	# it persistant check box is enabled
			persistant = ",f:01"		# add persistant flag
		else:
			persistant = ""
		return "}Cw,d:" + self.currentRegister + ",b:" + self.bin_to_hex32(self.bitList) + persistant + "!" # configuration write command + data in bitList 


	# writes configuration based on current_bit value (current_bit is bit to be changed)
	def writeConfiguration(self, current_bit):
		if self.currentRegister != 'none':	# current register should not be none
			if current_bit > -1:		# if current_bit is more than -1, this is for enable/disable form
				self.bitList[31-self.currentBit] = current_bit	# set proper value to this bit

			# otherwise, we dont need current bit
			self.gen_str_lbl.set_text("Generated string: " + self.genSendString().replace('\n',' '))
			self.communicator.sendData(self.genSendString()+'\r\n')
			self.rt_str_lbl.set_text("Returned string: " + self.communicator.retriveData().replace('\n',' '))
		else:
			self.clear_status_labels()
		
	# initialisation
	def __init__(self, communicator):
		self.init_bitlist()						# init bit list

		self.communicator = communicator				# init communicator
		self.init_frame()						# init frame
		self.init_table(2, 2)						# init table

		# generic scrolled window
		sw = gtk.ScrolledWindow()	
		sw.set_size_request(250, 15)				# scrolled window - for tree
		sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		self.table.attach(sw, 0, 1, 0, 1)
		sw.show()

		self.store = gtk.TreeStore(str, str, int, int, str)		# tree

		# 0 - dont draw control
 		# 1 - true/false
		# 2 - dropdown
		# 3 - entry
		
		# Querying suported registries
		self.communicator.sendData('}Cr!\r\n')
		regQuery = self.communicator.retriveData()
		
		regSupported = ()								# avalible registers on device
		
		for i, char in enumerate(regQuery):
			if (regQuery[i:][:2] == "d:") or (regQuery[i:][:2] == "D:"):
				regSupported  += (regQuery[i+2:][:3],)				# avalible registers on device
		
		root = self.store.append(None, ['Reader configuration', 'none', 0, 0, ''])	# root of tree
		treeGenModule.configTree.buildTree(root, self.store, regSupported)		# call func that bulds tree, based on avalible registers


		self.treeview = gtk.TreeView(self.store)					# set store to tree view
		tvcolumn = gtk.TreeViewColumn('Configuration')					# and column
		self.treeview.append_column(tvcolumn)						# and append column

		self.treeview.connect("cursor-changed", self.on_cursor_changed)			# and... connect event

		cell = gtk.CellRendererText()							# set cell renderer
		tvcolumn.pack_start(cell, True)							# pack column
		tvcolumn.add_attribute(cell, 'text', 0)						# add atribute
		sw.add_with_viewport(self.treeview)
		self.treeview.show()								# show tree

		# fixed container
		self.fixed = gtk.Fixed()							# set fixed container for config widgets
		self.table.attach(self.fixed, 1, 2, 0, 1)					# attach it to table
#		self.fixed.set_size_request(5, 15)
		self.fixed.show()								# show it

		# and fixed child of fixed container 
		self.fixedChild = gtk.Fixed()	
#		self.fixedChild.set_size_request(5, 15)						# child of fixed - so it can be turned off
		self.fixed.put(self.fixedChild, 0, 0)						# put it to his parent - fixed

		# title label
		self.titleLabel = gtk.Label("title")						# name of configruation option
	#	self.titleLabel.set_size_request(15, 15)	
		self.titleLabel.show()								# show this label

		# radio buttons
		self.radioEnable = gtk.RadioButton(None, "Enabled")
		self.radioEnable.connect("pressed", self.on_radio_enable)

		self.radioDisable = gtk.RadioButton(self.radioEnable, "Disabled", 1)
		self.radioDisable.connect("pressed", self.on_radio_disable)

		# check button
		self.check = gtk.CheckButton("Add persistant flag")
		self.check.show()

		# combobox
		self.combo_box = gtk.VBox(False, 10)
		self.combo_store = gtk.ListStore(str)
		self.combo = gtk.ComboBox(self.combo_store)
		self.combo.show()
		combo_cell = gtk.CellRendererText()
		self.combo.pack_start(combo_cell, True)
		self.combo.add_attribute(combo_cell, 'text', 0)
#		self.combo.connect("changed", self.on_combo_changed)
		button = gtk.Button("Save")
		button.connect("clicked", self.on_combo_changed)
		button.show()
		self.combo_box.pack_start(self.combo, False, False)
		self.combo_box.pack_start(button,False, False)


#		self.combo.append_text("test")

		# entry
		self.entry_box = gtk.VBox(False, 10)
		self.entry = gtk.Entry()
		self.entry.show()
		button = gtk.Button("Save")
#		button.add(self.entry_box)
		button.connect("clicked", self.on_text_changed)
		button.show()
		self.entry_box.pack_start(self.entry, False, False)
		self.entry_box.pack_start(button, False, False)

		# layout with radio buttons
		self.fixedChild.put(self.titleLabel, 10, 10)					# pack label
		self.fixedChild.put(self.radioEnable, 10, 50)					# pack enable button
		self.fixedChild.put(self.radioDisable, 10, 80)					# pack disable button
		self.fixedChild.put(self.check, 10, 120)					# pack check button
		self.fixedChild.put(self.combo_box, 10, 50)						# pack combo
		self.fixedChild.put(self.entry_box, 10, 50)					# pack combo

		# generated string label
		self.gen_str_lbl = gtk.Label("Generated string: ")				# create label for generated string
		self.table.attach(self.gen_str_lbl, 0, 2, 1, 2, gtk.FILL, gtk.SHRINK, 0, 10)	# attach it to table
		self.gen_str_lbl.set_alignment(0,0.1)						# set alignment of text
		self.gen_str_lbl.set_line_wrap(True)
		self.gen_str_lbl.show()								# show label

		# returned command label
		self.rt_str_lbl = gtk.Label("Returned string: ")				# create label for returned string
		self.table.attach(self.rt_str_lbl, 0, 2, 2, 3, gtk.FILL, gtk.FILL)		# attach it to table
		self.rt_str_lbl.set_alignment(0,0.1)						# set alignment of text
		self.rt_str_lbl.set_line_wrap(True)
		self.rt_str_lbl.set_single_line_mode(True)
		self.rt_str_lbl.set_max_width_chars(1)
		self.rt_str_lbl.show()	

		self.show_table_frame()								# show table and frame





